//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using OpenLS.Core;
using OpenLS.Spreadsheet.Drawing;

namespace OpenLS.Spreadsheet.UI
{
    internal class WorksheetPaginator
    {
        private readonly double _factor;
        private readonly PageOrder _pageOrder;
        private readonly Size _pageSize;
        private readonly Worksheet _worksheet;

        private WorksheetPaginator(Worksheet worksheet, Size pageSize, PageOrder pageOrder)
        {
            _worksheet = worksheet;
            _pageSize = pageSize;
            _pageOrder = pageOrder;
        }

        private static bool Fits(Worksheet worksheet, int scale, int horizontalPageCount, int verticalPageCount)
        {
            if (horizontalPageCount == 0 && verticalPageCount == 0)
                return true;
            var paginator = new WorksheetPaginator(worksheet, GetSize(worksheet, scale), PageOrder.DownThenOver);
            return paginator.Fits(horizontalPageCount, verticalPageCount);
        }

        private bool Fits(int horizontalPageCount, int verticalPageCount)
        {
            Contract.Requires(horizontalPageCount > 0 || verticalPageCount > 0);
            List<WorksheetPage> pages = Enumerable.ToList(GetPages());
            IEnumerable<CellAddress> rangeAddresses = Enumerable.Select(pages, p => p.RangeAddress.TopLeft);
            IEnumerable<int> rows = Enumerable.Distinct(Enumerable.Select(rangeAddresses, ra => ra.Row));
            IEnumerable<int> columns = Enumerable.Distinct(Enumerable.Select(rangeAddresses, ra => ra.Column));
            if (horizontalPageCount > 0 && Enumerable.Count(columns) > horizontalPageCount)
                return false;
            if (verticalPageCount > 0 && Enumerable.Count(rows) > verticalPageCount)
                return false;
            return true;
        }

        public static WorksheetPaginator GetPaginator(Worksheet worksheet)
        {
            Size size = GetSize(worksheet, null);
            if (worksheet.Properties != null && worksheet.Properties.PageSetupProperties != null &&
                worksheet.Properties.PageSetupProperties.FitToPage)
            {
                int horizontalPageCount = 0;
                int verticalpageCount = 0;
                if (worksheet.PageSetup != null)
                {
                    horizontalPageCount = worksheet.PageSetup.FitToWidth;
                    verticalpageCount = worksheet.PageSetup.FitToHeight;
                }
                if (horizontalPageCount > 0 || verticalpageCount > 0)
                {
                    int scale;
                    for (scale = 100; scale > 1; scale = scale/2)
                    {
                        if (Fits(worksheet, scale, horizontalPageCount, verticalpageCount))
                        {
                            break;
                        }
                    }
                    if (Fits(worksheet, scale, horizontalPageCount, verticalpageCount))
                        scale = Math.Min(scale*2, 100);

                    for (; scale > 1; scale--)
                    {
                        if (Fits(worksheet, scale, horizontalPageCount, verticalpageCount))
                        {
                            break;
                        }
                    }
                    scale = Math.Min(Math.Max(scale, 10), 400);
                    worksheet.PageSetup.Scale = scale;
                    //\\ Contract.Assert(GetPageCount(worksheet, scale) <= pageCount);
                    /*  Contract.Assert(Enumerable.Count(
                        new WorksheetPaginator(worksheet, GetSize(worksheet, null), worksheet.PageSetup.PageOrder).
                            GetPages()) <= pageCount);
                   * */
                }
                else
                {
                }
            }
            return new WorksheetPaginator(worksheet, GetSize(worksheet, null), worksheet.PageSetup.PageOrder);
        }

        internal static Size GetSize(Worksheet worksheet, int? scale)
        {
            Size result = PaperSizeToSizeConverter.GetSize(worksheet.PageSetup.PaperSize,
                                                           worksheet.PageSetup.Orientation);
            PageMargins pageMargins = worksheet.PageMargins;
            if (pageMargins != null)
            {
                result = new Size(result.Width - pageMargins.Left - pageMargins.Right,
                                  result.Height - pageMargins.Header - pageMargins.Top - pageMargins.Bottom -
                                  pageMargins.Footer);
            }
            double factor = 100d/scale.GetValueOrDefault(worksheet.PageSetup.Scale);
            if (worksheet.PageSetup != null && worksheet.PageSetup.FitToHeight == 0 &&
                worksheet.PageSetup.FitToWidth == 0)
            {
                if (worksheet.Properties != null && worksheet.Properties.PageSetupProperties != null &&
                    worksheet.Properties.PageSetupProperties.FitToPage)
                    factor = 1;
            }
            result = new Size(result.Width*factor, result.Height*factor);
            return result;
        }

        private static int GetLastRow(Anchor anchor)
        {
            if (anchor is TwoCellAnchor)
            {
                var two = (TwoCellAnchor) anchor;
                int result = two.SecondCell.Row;

                return two.SecondCell.Row;
            }
            if (anchor is OneCellAnchor)
            {
                var one = (OneCellAnchor) anchor;
                int result = one.Cell.Row;
                double height = one.ActualHeight;
                Row row = one.Worksheet.Rows[result];
                while (height > 0)
                {
                    height -= row.ActualHeight;
                    row = one.Worksheet.Rows[row.Index + 1];
                }
                return row.Index;
            }
            var a = (AbsoluteAnchor) anchor;
            return a.Worksheet.Rows.GetIndexForOffset(a.ActualHeight);
        }

        private static int GetLastColumn(Anchor anchor)
        {
            if (anchor is TwoCellAnchor)
            {
                var two = (TwoCellAnchor) anchor;
                return two.SecondCell.Column;
            }
            if (anchor is OneCellAnchor)
            {
                var one = (OneCellAnchor) anchor;
                int result = one.Cell.Column;
                double height = one.ActualHeight;
                Column column = one.Worksheet.Columns[result];
                while (height > 0)
                {
                    height -= column.ActualWidth;
                    column = one.Worksheet.Columns[column.Index + 1];
                }
                return column.Index;
            }
            var a = (AbsoluteAnchor) anchor;
            return a.Worksheet.Columns.GetIndexForOffset(a.ActualWidth);
        }

        public IEnumerable<WorksheetPage> GetPages()
        {
            int minRow;
            int minColumn;
            int maxRow;
            int maxColumn;
            Range range = getPrintArea();
            if (range != null && range.Worksheet == _worksheet)
            {
                minRow = range.TopLeftCell.Row.Index;
                minColumn = range.TopLeftCell.Column.Index;
                maxRow = minRow + range.RowSpan - 1;
                maxColumn = minColumn + range.ColumnSpan - 1;
            }
            else
            {
                minRow = 0;
                minColumn = 0;
                IEnumerable<Cell> allCells = Enumerable.Where(_worksheet.GetCells(), c => !c.IsVolatile && !c.IsEmpty);
                maxRow = Enumerable.Max(Enumerable.Select(allCells, c => c.Row.Index), 0);
                maxColumn = Enumerable.Max(Enumerable.Select(allCells, c => c.Column.Index), 0);
                maxRow = Math.Max(maxRow, Enumerable.Max(Enumerable.Select(_worksheet.Anchors, GetLastRow), 0));
                maxColumn = Math.Max(maxColumn, Enumerable.Max(Enumerable.Select(_worksheet.Anchors, GetLastColumn), 0));
            }
            IEnumerable<int> columnIndices = GetBreaks(minColumn,
                                                       Enumerable.Select(_worksheet.Columns,
                                                                         column => column.ActualWidth), _pageSize.Width,
                                                       maxColumn);

#if DEBUG
            Debug.WriteLine("Last Row BottomOffset" + _worksheet.Rows[maxRow].BottomOffset);
#endif
            IEnumerable<int> rowIndices = GetBreaks(minRow, Enumerable.Select(_worksheet.Rows, row => row.ActualHeight),
                                                    _pageSize.Height, maxRow);

#if DEBUG
            var test = Enumerable.ToList(rowIndices);

            List<int> columnIndicesList = Enumerable.ToList(columnIndices);
            
            Contract.Assert((columnIndicesList[0]) == minColumn);
            Contract.Assert((columnIndicesList[columnIndicesList.Count - 1]) == maxColumn + 1);
            if (Enumerable.Count(rowIndices) <= 1)
            {
                rowIndices = GetBreaks(minRow, Enumerable.Select(_worksheet.Rows, row => row.ActualHeight),
                                       _pageSize.Height, maxRow);
            }
            Contract.Assert(Enumerable.Count(rowIndices) > 1);
#endif
            int number = 1;
            PaginationResult paginationResult = new PaginationResult();
            if (_pageOrder == PageOrder.DownThenOver)
            {
                foreach (
                    var pair in
                        Enumerable.CrossProduct(Enumerable.GetNeighbors(columnIndices),
                                                Enumerable.GetNeighbors(rowIndices)))
                {
                    int row1 = pair.Second.First;
                    int column1 = pair.First.First;
                    int row2 = pair.Second.Second - 1;
                    int column2 = pair.First.Second - 1;
                    if (range != null)
                    {
                        row1 = Math.Max(range.Row, row1);
                        column1 = Math.Max(range.Column, column1);
                        row2 = Math.Max(range.Row + range.RowSpan - 1, row2);
                        column2 = Math.Max(range.Column + range.ColumnSpan - 1, column2);
                    }
                    var rangeCandidate = new MultiCellsRange(_worksheet[row1, column1],
                                                             _worksheet[
                                                                 row2, column2
                                                                 ]);
                    Debug.WriteLine("yielding page " + rangeCandidate.Label);
                    paginationResult.PageCount = number;
                    var page = new WorksheetPage(rangeCandidate, number++, paginationResult);
                    Contract.Assert(page.DesiredWidth < _pageSize.Width || range != null);
                    yield return page;
                }
            }
            else
            {
                foreach (
                    var pair in
                        Enumerable.CrossProduct(Enumerable.GetNeighbors(rowIndices),
                                                Enumerable.GetNeighbors(columnIndices)))
                {
                    paginationResult.PageCount = number;
                    yield return new WorksheetPage(new MultiCellsRange(_worksheet[pair.First.First, pair.Second.First],
                                                                       _worksheet[
                                                                           pair.First.Second - 1, pair.Second.Second - 1
                                                                           ]), number++, paginationResult);
                }
            }
        }

        private Range getPrintArea()
        {
            DefinedName definedName = Enumerable.FirstOrDefault(_worksheet.Workbook.DefinedNames,
                                                                d => d.Name == BuiltInDefinedNames.PrintArea);
            if (definedName != null)
                return definedName.Value as Range;
            return null;
        }

        private static IEnumerable<int> GetBreaks(int minIndex, IEnumerable<double> sizes, double maxPageSize,
                                                  int maxIndex)
        {
            double actualSize = 0;
            int index = minIndex;
            yield return index;
            foreach (double d in sizes)
            {
                if (actualSize + d > maxPageSize)
                {
                    yield return index;
                    actualSize = d;
                    // if (index == maxIndex)
                    //{
                    //    break;
                    // }
                }
                else
                    actualSize += d;
                index++;
                if (index > maxIndex)
                {
                    //    yield return index ;
                    break;
                }
            }
            yield return maxIndex + 1;
        }
    }
}